import time
from ganges.text.block import TextBlock


class CType(object):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return "<%s %s>" % (self.__class__.__name__, self.name)
    def get_dependencies(self):
        return ()
    def reference(self):
        return self.name
    def define(self):
        pass
    def declare(self):
        pass

class BuiltinType(CType):
    pass

class ExternType(CType):
    pass

class Pointer(CType):
    def __init__(self, type, levels = 1):
        assert levels >= 1
        CType.__init__(self, type.reference() + " " + "*" * levels)
        self.type = type
    def get_dependencies(self):
        return (self.type,)

cvoid = BuiltinType("void")
cpvoid = Pointer(cvoid)
cint = BuiltinType("int")
cuint = BuiltinType("unsigned int")
cpint = Pointer(cint)
cchar = BuiltinType("char")
cuchar = BuiltinType("unsigned char")
cpchar = Pointer(cchar)
cshort = BuiltinType("short")
cushort = BuiltinType("unsigned short")
cpshort = Pointer(cshort)
clong = BuiltinType("long")
culong = BuiltinType("unsigned long")
cplong = Pointer(clong)
cfloat = BuiltinType("float")
cpfloat = Pointer(cfloat)
cdouble = BuiltinType("double")
cpdouble = Pointer(cdouble)

def varspec(name, type, indexes = []):
    if indexes:
        return "%s %s[%s]" % (type.reference(), name, "][".join(str(i) for i in indexes))
    else:
        return "%s %s" % (type.reference(), name)

class VarDef(object):
    _id = 0
    def __init__(self, name, type, indexes = []):
        self.__class__._id += 1
        if name is None:
            name = "_var_%d" % (self._id,)
        self.name = name
        self.type = type
        self.indexes = indexes
    @classmethod
    def unique(cls, type, indexes = []):
        return cls(None, type, indexes)
    def __str__(self):
        return self.reference()
    def reference(self):
        return self.name
    def declare(self):
        return varspec(self.name, self.type, self.indexes)
    def emit(self):
        return varspec(self.name, self.type, self.indexes) + ";"

class Typedef(CType):
    def __init__(self, name, type, indexes = []):
        CType.__init__(self, name)
        self.type = type
        self.indexes = indexes
    def get_dependencies(self):
        return (self.type,)
    def declare(self):
        return "typedef %s;" % (varspec(self.name, self.type, self.indexes),)

class Struct(CType):
    def __init__(self, name, *members):
        CType.__init__(self, name)
        self.members = []
        for m in members:
            if isinstance(m, VarDef):
                self.members.append(m)
            else:
                self.members.append(VarDef(*m))
    def append(self, name, type, indexes = []):
        self.members.append(VarDef(name, type, indexes))
    def reference(self):
        return "struct " + self.name
    def declare(self):
        return self.reference() + ";"
    def define(self):
        blk = TextBlock("struct " + self.name, "{", dedent = 1)
        blk.create_child(*(m.emit() for m in self.members)) 
        blk.append("};")
        return blk
    def get_dependencies(self):
        return set(m.type for m in self.members)

def calc_depth(type, cache):
    if type not in cache:
        cache[type] = 0
        deps = type.get_dependencies()
        if deps:
            cache[type] = 1 + max(calc_depth(t, cache) for t in deps)
    return cache[type]

class Macro(object):
    def __init__(self, name, args = None, value = ""):
        self.name = name
        self.args = args
        self.value = value
    def define(self):
        args = "(" + ", ".join(args) + ")" if self.args is not None else ""
        return "#define %s%s %s" % (self.name, args, self.value)

class Function(object):
    def __init__(self, name, rettype, args = (), locals = (), body = None):
        self.name = name
        self.rettype = rettype
        self.args = []
        for a in args:
            if isinstance(a, VarDef):
                self.args.append(a)
            else:
                self.args.append(VarDef(*a))
        self.locals = list(locals)
        if body is None:
            body = CodeBlock()
        self.body = body
    def newvar(self, name, type, indexes = []):
        self.locals.append(VarDef(name, type, indexes))
        return self.locals[-1]
    def declare(self):
        args = ", ".join(a.declare() for a in self.args)
        return "%s %s(%s);" % (self.rettype.reference(), self.name, args)
    def define(self):
        args = ", ".join(a.declare() for a in self.args)
        blk = TextBlock("%s %s(%s)" % (self.rettype.reference(), self.name, args), dedent = 1)
        if self.locals:
            self.body.prepend("")
            for var in reversed(self.locals):
                self.body.prepend(var)
        blk.append(self.body.emit())
        return blk

class Expr(object):
    def __init__(self, fmttext, *args):
        if args:
            fmttext %= args
        self.text = fmttext
    def __str__(self):
        return self.text

class Statement(object):
    def __init__(self, text):
        self.text = text
    def emit(self):
        return self.text + ";"

class CodeBlock(object):
    def __init__(self, *stmts):
        self.stmts = list(stmts)
    def append(self, stmt):
        self.stmts.append(stmt)
    def prepend(self, stmt):
        self.stmts.insert(0, stmt)
    def emit(self):
        if self.stmts:
            blk = TextBlock("{", dedent = 1)
            blk2 = blk.create_child()
            for s in self.stmts:
                if isinstance(s, basestring):
                    blk2.append(s)
                else:
                    blk2.append(s.emit())
            blk.append("}")
        else:
            blk = TextBlock(";")
        return blk

class BlockStatement(Statement):
    def __init__(self, text, *stmts):
        Statement.__init__(self, text)
        self.body = CodeBlock(*stmts)
    def append(self, stmt):
        self.body.append(stmt)
    def emit(self):
        return TextBlock(self.text, self.body.emit(), dedent = 1)

class IfStmt(BlockStatement):
    def __init__(self, cond, *stmts):
        BlockStatement.__init__(self, "if (%s)" % (cond,), *stmts)

class ElifStmt(BlockStatement):
    def __init__(self, cond, *stmts):
        BlockStatement.__init__(self, "else if (%s)" % (cond,), *stmts)

class ElseStmt(BlockStatement):
    def __init__(self, *stmts):
        BlockStatement.__init__(self, "else" % (cond,), *stmts)

class WhileStmt(BlockStatement):
    def __init__(self, cond, *stmts):
        BlockStatement.__init__(self, "while (%s)" % (cond,), *stmts)

class ForStmt(BlockStatement):
    def __init__(self, init, cond, next, *stmts):
        BlockStatement.__init__(self, "for (%s; %s; %s)" % (init, cond, next), *stmts)

class AsgnStmt(Statement):
    def __init__(self, var, expr):
        if isinstance(var, VarDef):
            var = var.name
        Statement.__init__(self, "%s = %s" % (var, expr))

class CallStmt(Statement):
    def __init__(self, func, *args):
        if isinstance(func, Function):
            func = func.name
        Statement.__init__(self, "%s(%s)" % (func, ", ".join(str(a) for a in args)))

class Module(object):
    def __init__(self, name, c_file = None, h_file = None):
        self.name = name
        self.c_file = name + ".c" if c_file is None else c_file 
        self.h_file = name + ".h" if h_file is None else h_file 
        self.includes = []
        self.macros = {}
        self.types = {}
        self.functions = {}
    
    def __repr__(self):
        return "Module(%r)" % (self.name,)
    
    def include(self, filename, rel = False):
        if rel:
            filename = '"%s"' % (filename,)
        else:
            filename = '<%s>' % (filename,)
        self.includes.append(filename)
    
    def __contains__(self, name):
        return name in self.macros or name in self.types or name in self.functions 
    
    def __delitem__(self, name):
        if name in self.macros:
            del self.macros[name]
        elif name in self.types:
            del self.types[name]
        elif name in self.functions:
            del self.functions[name]
        raise KeyError(name)
    
    def __getitem__(self, name):
        if name in self.macros:
            return self.macros[name]
        if name in self.types:
            return self.types[name]
        if name in self.functions:
            return self.functions[name]
        raise KeyError(name)
    
    def __setitem__(self, name, value):
        if name in self:
            del self[name]
        if isinstance(value, Macro):
            self.macros[name] = value
        elif isinstance(value, CType):
            self.types[name] = value
        elif isinstance(value, Function):
            self.functions[name] = value
        else:
            raise ValueError("not a module-level element: %r" % (value,))
    
    def add(self, obj):
        if obj.name in self:
            raise ValueError("%s already exists" % (obj.name,))
        self[obj.name] = obj
    
    def generate_c(self):
        doc = TextBlock(
            "/*****************************************************************",
            " * Module %s (%s)" % (self.name, self.c_file),
            " * Generated on %s" % (time.asctime(),),
            " ****************************************************************/",
            '#include "%s"' % (self.h_file,),
            "",
        )
        for func in self.functions.itervalues():
            doc.append(func.define())
        return doc.render()
    
    def generate_h(self):
        doc = TextBlock(
            "/*****************************************************************",
            " * Module %s Header (%s)" % (self.name, self.h_file),
            " * Generated on %s" % (time.asctime(),),
            " ****************************************************************/",
            "#ifndef HEADER_%s_INCLUDED" % (self.name.upper(),),
            "#define HEADER_%s_INCLUDED" % (self.name.upper(),),
            "",
        )
        if self.includes:
            for fn in self.includes:
                doc.append("#include %s" % (fn,))
            doc.append("")
        if self.macros:
            doc.append("/*", " * MACROS", " */")
            for macro in self.macros.itervalues():
                doc.append(macro.define())
            doc.append("", "")
        if self.types:
            doc.append("/*", " * TYPE DECLARATIONS", " */")
            cache = {}
            types = sorted((calc_depth(t, cache), t) for t in self.types.itervalues())
            for d, t in types:
                doc.append(t.declare())
            doc.append("", "")
            doc.append("/*", " * TYPE DEFINITIONS", " */")
            for d, t in types:
                doc.append(t.define())
            doc.append("", "")
        if self.functions:
            doc.append("/*", " * FUNCTION PROTOTYPES", " */")
            for func in self.functions.itervalues():
                doc.append(func.declare())
            doc.append("", "")
        doc.append("#endif /* HEADER_%s_INCLUDED */" % (self.name.upper(),))
        return doc.render()
    
    def generate(self):
        return self.generate_h(), self.generate_c()
    
    def generate_files(self):
        htext, ctext = self.generate() 
        open(self.h_file, "w").write(htext)
        open(self.c_file, "w").write(ctext)



if __name__ == "__main__":
    mod = Module("moshe")
    date_t = Struct("date_t", 
        ("year", cint),
        ("month", cint),
        ("day", cint),
    )
    person_t = Struct("person_t", 
        ("name", cchar, [80]),
        ("id", cint),
        ("birth_date", date_t),
        ("child_count", cint),
    )
    person_t.append("children", Pointer(person_t))
    
    mod.add(person_t)
    mod.add(date_t)
    mod.add(Typedef("couple_t", person_t, [2]))
    mod.add(Macro("TRUE", value = "(0 == 0)"))
    
    f = Function("person_init", cvoid, args = [
        VarDef("self", Pointer(person_t)), VarDef("name", cpchar)]
    )
    f.newvar("i", cint)
    f.newvar("j", cint)
    mod.add(f)
    f.body.append(
        ForStmt("i = 0", "i < 10", "i++",
            AsgnStmt("j", "i * 2"),
            CallStmt("printf", '"i = %d"', "i"), 
        )
    )
    
    htext, ctext = mod.generate()
    print htext
    print "-" * 80
    print ctext





































